package base.file_io_demo.io_demo01;

import java.io.*;
import java.net.URL;


/**
 * FileReader、FileWriter 在进行文件读写时，要保持文件默认编码等同项目编码，就不会出现乱码问题。
 * 
 * 字节流可以处理任何数据，字符流只能处理字符数据
 */
public class IOTest {
	
	public static void main(String[] args) throws Exception {
		/*
		String s = "联通";
		System.out.println(s.getBytes("GBK").length); // 4
		System.out.println(s.getBytes("UTF-8").length); // 6
		*/
		
//		testCase1();
//		testCase2();
//		testCase3();
//		testCase4();
//		testCase5();
		

		byte[] datas = FileUtil.fileToByteArray("files/source/16MGBK.txt");
		System.out.println(datas.length);
		FileUtil.byteArrayToFile(datas, "files/target/16MGBK.txt");

		
//		testCase6();
		
		
	}
	
	/**
	 * 功能：拷贝一张图片，从指定的输入流中每次读取一个字节数据，并按照一个字节一个字节数据写出，字节拷贝不存在乱码问题
	 * 效率：2M的图片拷贝超10s
	 * 		16M GBK文件拷贝时长大约4-5min
	 * 
	 * 字节型节点流  FileInputStream  FileOutputStream
	 */
	public static void testCase1() throws Exception {
		FileInputStream in = new FileInputStream("files/source/GBK.txt");
//		byte[] b = new byte[1024];
//		int len = in.read(b);
//		System.out.println("读取字节数："+len); // 8，GBK.txt文件属性是GBK，因此读取一位中文字符是2字节
//		System.out.println(new String(b, 0, len, "UTF-8")); // 乱码
//		System.out.println(new String(b, 0, len, "GBK")); //小灵通xx
		
		
		FileOutputStream out = new FileOutputStream("files/target/GBK.txt");
		int read = -1;  // 存储读取到的每个字节数据
		while(-1 != (read=in.read())) {// 从指定的输入流中读取一个字节数据
			out.write(read);// 将指定的字节写入此文件输出流
		}
		in.close();
		out.close();
	}
	
	/**
	 * 功能：拷贝一张图片，从输入流中读取指定长度的字节数据,写出也是按照读取到的字节长度写出。-好处就是原版照搬，不会破坏文件导致乱码问题
	 * 效率：2M图片拷贝的时长不足1s
	 * 		100M文件拷贝时长差不多1s, 由此可见采用容器拷贝大大提升了效率
	 * 		GBK编码的文件拷贝也不会乱码（记事本打开）
	 * 字节型节点流  FileInputStream  FileOutputStream
	 */
	public static void testCase2() throws Exception {
		FileInputStream in = new FileInputStream("files/source/16MGBK.txt");
		FileOutputStream out = new FileOutputStream("files/target/16MGBK.txt");
		
		byte[] b = new byte[1024];// 构造一个数组容器，一次性可装1024*8 个bite
		int len = -1;  // 存储每次实际读取字节长度
		while(-1 != (len=in.read(b))){// 给容器塞入数据：从输入流中读取最多b.leng个字节的数据填充b[]，返回实际读取的字节长度。如果读取到文件末尾，返回-1
			out.write(b, 0, len);// 从容器中取数据并写如输出流：从b数组中的偏移量0角标开始的len个字节数据写到输出流中
		}
		in.close();
		out.close();
	}
	
	/**
	 * 字符型节点流  FileReader FileWriter
	 * 字符型包装流  BufferedWriter PrintWriter
	 * 
	 * 功能：FileWriter会追加文件内容，但不会换行追加，可以在FileWriter基础上构造一个PrintWriter，调用println()换行追加文件内容
	 * 效率：
	 * 	100M 字符文件拷贝时长大约3s。字符拷贝，待拷贝文件必须与项目编码等同，才不会出现乱码问题。
	 */
	public static void testCase3() throws Exception {
		BufferedReader reader = new BufferedReader(new FileReader("files/source/100M-utf8.txt"));
//		BufferedReader reader = new BufferedReader(new FileReader("files/source/16MGBK.txt"));

//		BufferedWriter writer = new BufferedWriter(new FileWriter("files/target/liantong.txt",true));// true以追加方式写入文件。追加时不会换行，差评
		PrintWriter writer = new PrintWriter(new FileWriter("files/target/100M-utf8.txt", true));//println() 换行输出
		
		String line = null;
		while(null != (line=reader.readLine())) {
			writer.println(line);// 每输出一行后产生一空行。如果文件已有内容，且文件末尾不是一空行，则首次追加在文件末尾。
		}
		reader.close();
		writer.close();
	}
	
	/**
	 * https://www.qqxiuzi.cn/bianma/zifuji.php 字符二进制码
	 *
	 * 字符型节点流  FileReader FileWriter
	 * 
	 * 功能：拷贝文件：以项目默认字符编码格式一个字符一个字符的拷贝一份文件。(假如项目是UTF8编码,那么每次读取3个byte认为是一个字符,如果实际文件是GBK编码,就会出现乱码)
	 * 当涉及到计算机的最小存储单位（字节）到字符转换时，就涉及到编码参照问题。默认参照项目编码.
	 * 解决乱码方案就是：在FileInputStream上构造一个InputStreamReader,可以指定读取字符的编码
	 * 
	 * 效率：
	 * 	16M文件拷贝时长大约2s，如果待拷贝文件与项目编码不一致，文件会乱码
	 * 	80M文件拷贝时长大约10s
	 *
	 * 	InputStream 和 Reader 的read()方法都是一个字节(前者)或者一个字符(后者)的读取,返回的是读取的字节或字符数据对应的int类型值,
	 * 	而read(char[])方法是将数据读入char[]代表的缓冲区,返回实际读取的字节或字符长度.
	 */
	public static void testCase4() throws Exception {
		// 相当于我拿着FileReader工具以UTF8编码(3byte)将GBK编码(2byte)二进制文件读取成了UTF8二进制,写的时候写成了UTF8文件,以UTF8编码展示的文件肯定就乱码了.
		FileReader reader = new FileReader("files/source/16MGBK.txt");//将GBK编码的文件识别成项目的UTF-8字符编码会出现乱码
		FileWriter writer = new FileWriter("files/target/20M-utf8.txt");
		int read = -1; // 存储每次读取到的一个字符对应的int 整数
		while(-1 != (read = reader.read())) {// 每次读取一个字符，并将指针指向下一位置
			writer.write(read);
		}
		reader.close();
		writer.close();
	}

	/**
	 * 字符转换流  InputStreamReader
	 * 字节型节点流  FileInputStream  FileOutputStream
	 * 功能：拷贝文件：指定字符编码和字符长度对文件进行拷贝
	 * 效率：
	 * 	80M GBK文件转储成UTF-8类型文件，大约时长3s
	 */
	public static void testCase5() throws Exception{
		// 在FileInputStream上构造一个InputStreamReader,可以指定读取字符的编码，可以指定每次读取字符个数
		// new FileInputStream("files/source/80MGBK.txt") 将80MGBK.txt这个GBK编码文件读到字节流中，文件属性是GBK-会将一个中文字符拆成2个字节存储，实际硬盘中对于GBK字符编码就是每两个字节存储一位中文字符
		// new InputStreamReader(inputStream,"GBK") 将inputStream字节流，转换成GBK编码格式的字符流。 
		InputStreamReader reader = new InputStreamReader(new FileInputStream("files/source/80MGBK.txt"), "GBK");
		OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("files/target/utf8.txt"), "UTF-8");
		char[] b = new char[1024]; // 构造一个数组容器，将来里面存储的数据类似['苹','果','a','p']
		int len = -1;  // 存储每次实际读取字符长度
		while(-1 != (len = reader.read(b))) {// 给容器塞入数据：从输入流中读取最多b.leng个字符的数据填充b[]，返回实际读取的字符长度。如果读取到文件末尾，返回-1
			writer.write(b, 0, len);// 从容器中取数据并写到输出流：从b数组中的偏移量0角标开始的len个字符数据写到输出流中
			// 比如['苹','果','a','p']，看到一个中文字符'苹'用UTF-8编码写出时，就用3个字节写出
		}
		reader.close();
		writer.close();
	}
	
	
	/**
	 * 案例：接收标准输入设备输入，并将输入在控制台输出。
	 * 功能：在控制台输入，回车后控制台会显示你的输入。
	 */
	public static void testCase6() {
		try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))){
			String line = null;
			while(null != (line=reader.readLine())) {
				writer.write(line);
				writer.newLine();
				writer.flush();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 下载网站源码
	 */
	/*
	 * 缺陷：字节流对于中文字符处理出现乱码，一个中文字符根据编码方式不同占用多个字节，不能每读取一个字节就转换成字符型。
	 */
	//@Test
	public void download() {
		try (InputStream input = new URL("https://www.baidu.com").openStream();){
			int read = -1;
			while(-1 != (read = input.read())) {
				System.out.print((char)read);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * InputStreamReader: 在进行字节流向字符流转换时，不指定参考字符编码(将几个字节数据转换为一个字符数据)，默认使用项目字符编码
	 */
	//@Test
	public void download2() {
//		try (InputStreamReader reader = new InputStreamReader(new URL("https://www.163.com").openStream())){// 163网站是gbk编码的
		try (InputStreamReader reader = new InputStreamReader(new URL("http://www.baidu.com").openStream(),"UTF-8")){
			int read = -1;
			while(-1 != (read = reader.read())) {
				System.out.print((char)read);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/*
	 * 使用缓冲流优化效率
	 */
	@org.junit.Test
	public void download3() {
		try (BufferedReader reader = 
				new BufferedReader(
						new InputStreamReader(
								new URL("http://www.baidu.com").openStream(),"UTF-8"));//指定每读取3个字节转换为一个字符
			 BufferedWriter writer = 
					 new BufferedWriter(
							 new OutputStreamWriter(
									 new FileOutputStream("files/target/baidu.txt"),"UTF-8"))){//指定写成UTF-8编码文件
			String line = null;
			while(null != (line=reader.readLine())) {
				writer.write(line);
				writer.newLine();
			}
			writer.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
